﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Windows;

using Microsoft.Practices.Composite.Presentation.Regions;
using Microsoft.Practices.Composite.Presentation.Regions.Behaviors;
using Microsoft.Practices.Composite.Regions;

namespace DanielVaughan.Calcium.Modularity
{
	public class CustomRegionManagerRegistrationBehavior : RegionBehavior, IHostAwareRegionBehavior
    {
        WeakReference attachedRegionManagerWeakReference;
        DependencyObject hostControl;

        /// <summary>
        /// Initializes a new instance of <see cref="RegionManagerRegistrationBehavior"/>.
        /// </summary>
		public CustomRegionManagerRegistrationBehavior()
        {
            this.RegionManagerAccessor = new DefaultRegionManagerAccessor();
        }

        /// <summary>
        /// Provides an abstraction on top of the RegionManager static members.
        /// </summary>
        public IRegionManagerAccessor RegionManagerAccessor { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
        /// </summary>
        /// <value>A <see cref="DependencyObject"/> that the <see cref="IRegion"/> is attached to.
        /// This is usually a <see cref="FrameworkElement"/> that is part of the tree.</value>
        /// <exception cref="InvalidOperationException">When this member is set after the <see cref="IRegionBehavior.Attach"/> method has being called.</exception>
        public DependencyObject HostControl
        {
            get
            {
                return hostControl;
            }
            set
            {
                if (IsAttached)
                {
                    throw new InvalidOperationException("Host cannot be set after attach.");
                }
                hostControl = value;
            }
        }

        /// <summary>
        /// When the <see cref="IRegion"/> has a name assigned, the behavior will start monitoring 
        /// the ancestor controls in the element tree
        /// to look for an <see cref="IRegionManager"/> where to register the region in.
        /// </summary>
        protected override void OnAttach()
        {
            if (string.IsNullOrEmpty(this.Region.Name))
            {
                Region.PropertyChanged += Region_PropertyChanged;
            }
            else
            {
                StartMonitoringRegionManager();
            }
        }

        private void Region_PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "Name" && !string.IsNullOrEmpty(this.Region.Name))
            {
                this.Region.PropertyChanged -= this.Region_PropertyChanged;
                this.StartMonitoringRegionManager();
            }
        }

        private void StartMonitoringRegionManager()
        {
            this.RegionManagerAccessor.UpdatingRegions += this.OnUpdatingRegions;
            this.TryRegisterRegion();
        }

        private void TryRegisterRegion()
        {
            DependencyObject targetElement = this.HostControl;
            if (targetElement.CheckAccess())
            {
                IRegionManager regionManager = this.FindRegionManager(targetElement);

                IRegionManager attachedRegionManager = this.GetAttachedRegionManager();

                if (regionManager != attachedRegionManager)
                {
                    if (attachedRegionManager != null)
                    {
                        this.attachedRegionManagerWeakReference = null;
                        attachedRegionManager.Regions.Remove(this.Region.Name);
                    }

                    if (regionManager != null)
                    {
                        this.attachedRegionManagerWeakReference = new WeakReference(regionManager);

                        regionManager.Regions.Add(this.Region);
                    }
                }
            }
        }

        /// <summary>
        /// This event handler gets called when a RegionManager is requering the instances of a region to be registered if they are not already.
        /// <remarks>Although this is a public method to support Weak Delegates in Silverlight, it should not be called by the user.</remarks>
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The arguments.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", Justification = "This has to be public in order to work with weak references in partial trust or Silverlight environments.")]
        public void OnUpdatingRegions(object sender, EventArgs e)
        {
            this.TryRegisterRegion();
        }

        private IRegionManager FindRegionManager(DependencyObject dependencyObject)
        {
            var regionmanager = this.RegionManagerAccessor.GetRegionManager(dependencyObject);
            if (regionmanager != null)
            {
                return regionmanager;
            }

            DependencyObject parent = null;
#if SILVERLIGHT
            //parent = System.Windows.Media.VisualTreeHelper.GetParent(dependencyObject);
                    FrameworkElement frameworkElement = dependencyObject as FrameworkElement;
                    if (frameworkElement != null)
                    {
                        parent = frameworkElement.Parent;
                    }
#else
            parent = LogicalTreeHelper.GetParent(dependencyObject);

			#region Customized for ControlTemplates [DV]
			/* [DV] Customized to allow styling. 
			 * Top level ancestor will not have a parent if it is in a ControlTemplate. */
			if (parent == null)
			{
				var temp = dependencyObject as FrameworkElement;
				if (temp != null)
				{
					parent = temp.TemplatedParent;
				}
			}
			#endregion

#endif
			if (parent != null)
            {
                return this.FindRegionManager(parent);
            }

            return null;
        }

        IRegionManager GetAttachedRegionManager()
        {
            if (this.attachedRegionManagerWeakReference != null)
            {
                return this.attachedRegionManagerWeakReference.Target as IRegionManager;
            }

            return null;
        }
    }

	class DefaultRegionManagerAccessor : IRegionManagerAccessor
	{
		/// <summary>
		/// Notification used by attached behaviors to update the region managers appropriatelly if needed to.
		/// </summary>
		/// <remarks>This event uses weak references to the event handler to prevent this static event of keeping the
		/// target element longer than expected. For security reasons, to use weak delegates in Silverlight you must provide
		/// a delegate that is available in the public API of the class (no private or anonymous delegates allowed).</remarks>
		public event EventHandler UpdatingRegions
		{
			add
			{
				RegionManager.UpdatingRegions += value;
			}
			remove
			{
				RegionManager.UpdatingRegions -= value;
			}
		}

		/// <summary>
		/// Gets the value for the RegionName attached property.
		/// </summary>
		/// <param name="element">The object to adapt. This is typically a container (i.e a control).</param>
		/// <returns>The name of the region that should be created when 
		/// the RegionManager is also set in this element.</returns>
		public string GetRegionName(DependencyObject element)
		{
			return element.GetValue(RegionManager.RegionNameProperty) as string;
		}

		/// <summary>
		/// Gets the value of the RegionName attached property.
		/// </summary>
		/// <param name="element">The target element.</param>
		/// <returns>The <see cref="IRegionManager"/> attached to the <paramref name="element"/> element.</returns>
		public IRegionManager GetRegionManager(DependencyObject element)
		{
			return element.GetValue(RegionManager.RegionManagerProperty) as IRegionManager;
		}
	}
}
